package live.zs.securitytokenauthorize.config;

import live.zs.securitytokenauthorize.filter.JwtAuthenticationTokenFilter;
import live.zs.securitytokenauthorize.security.JwtTokenManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;

import javax.servlet.http.HttpServletResponse;


/**
 * @author madison
 * @description
 * @date 2021/7/15 20:07
 */
// 当我们想要开启spring方法级安全时，只需要在任何 @Configuration实例上使用 @EnableGlobalMethodSecurity 注解就能达到此目的。
// 同时这个注解为我们提供了prePostEnabled 、securedEnabled 和 jsr250Enabled 三种不同的机制来实现同一种功能
// prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解。
// 从名字就可以看出@PreAuthorize 注解会在方法执行前进行验证，而 @PostAuthorize 注解会在方法执行后进行验证。

//Secured：
//@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured，并且只有那些角色/权限的用户才可以调用该方法。
//@Secured缺点（限制）就是不支持Spring EL表达式。不够灵活。并且指定的角色必须以ROLE_开头，不可省略。
// 该注解功能要简单的多，默认情况下只能基于角色（默认需要带前缀 ROLE_）集合来进行访问控制决策。
// 该注解的机制是只要其声明的角色集合（value）中包含当前用户持有的任一角色就可以访问
// 。也就是 用户的角色集合和 @Secured 注解的角色集合要存在非空的交集。 不支持使用 SpEL 表达式进行决策。

//jsr250E：
//启用 JSR-250 安全控制注解，这属于 JavaEE 的安全规范（现为 jakarta 项目）。一共有五个安全注解。
// 如果你在 @EnableGlobalMethodSecurity 设置 jsr250Enabled 为 true ，就开启了 JavaEE 安全注解中的以下三个：
//
//1.@DenyAll： 拒绝所有访问
//
//2.@RolesAllowed({“USER”, “ADMIN”})： 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省略前缀ROLE_，实际的权限可能是ROLE_ADMIN
//
//3.@PermitAll： 允许所有访问
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public static final String LOGIN_ENTRY_POINT = "/auth/login";
    public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";

    @Autowired
    private JwtTokenManager tokenManager;
    @Value("${security.ignore.urls}")
    private String ignoreUrls;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers(ignoreUrls.split(SECURITY_IGNORE_URLS_SPILT_CHAR));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers(
                header -> header.disable()  //禁用缓存
        ).sessionManagement(
                session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)   // 基于token，所以不需要session
        ).csrf(
                csrf -> csrf.disable()   //CSRF - Cross-Site Request Forgery - 跨站请求伪造
        ).authorizeRequests(
                auth -> auth.antMatchers(LOGIN_ENTRY_POINT).permitAll()
                        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                        .anyRequest().authenticated()
        ).addFilterBefore(
                new JwtAuthenticationTokenFilter(tokenManager),     // 添加JWT filter
                UsernamePasswordAuthenticationFilter.class
        ).exceptionHandling(
                handling -> handling.authenticationEntryPoint(jwtAuthenticationEntryPoint())    //添加自定义未授权和未登录结果返回
        );

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService())
                .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    CustomUserDetailsService customUserDetailsService() {
        return new CustomUserDetailsService();
    }

    AuthenticationEntryPoint jwtAuthenticationEntryPoint() {
        return (request, response, e) -> {
            log.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI());
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        };
    }
}
